/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include <devfs.h>
/* includes (standard library, system) ---------------------------------------*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_config.h>
#include <x_compiler.h>
#include <x_module.h>
#include <x_warn.h>
#include <x_bug.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/

struct devfs_file {
    dlist_t node;
    const struct devfs_ops *ops;
    uintptr_t data;
    const char *name;
};

/* macro ---------------------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/

static dlist_t devfs_files = DLIST_INIT(devfs_files);

/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

static struct devfs_file *__get_devfile(const char *name)
{
    dlist_t *iter;
    struct devfs_file *file;

    WARN_ON(strchr(name, '/') != NULL, return NULL, "Invalid device name!");

    dlist_foreach(iter, &devfs_files)
    {
        file = container_of(iter, struct devfs_file, node);

        if (strcmp(name, file->name) == 0) {
            return file;
        }
    }

    WARN("File not found!");

    return NULL;
}

static int devfs_open(struct file *fp, const char *path, int flags, int mode)
{
    struct devfs_file *file = __get_devfile(path);

    if (file == NULL) {
        errno = ENOENT;
        return -1;
    }

    fp->fl_data = (uintptr_t)file;

    return file->ops->open == NULL ? 0 : file->ops->open(file->data);
}

static int devfs_close(struct file *fp)
{
    struct devfs_file *file = (struct devfs_file *)fp->fl_data;

    return file->ops->close == NULL ? 0 : file->ops->close(file->data);
}

static int devfs_read(struct file *fp, char *buf, size_t nbyte)
{
    struct devfs_file *file = (struct devfs_file *)fp->fl_data;

    WARN_ON(file->ops->read == NULL, errno = ENOTSUP; return -1, "File has no <read> method!");

    return file->ops->read(file->data, buf, nbyte);
}

static int devfs_write(struct file *fp, const char *buf, size_t nbyte)
{
    struct devfs_file *file = (struct devfs_file *)fp->fl_data;

    WARN_ON(file->ops->write == NULL, errno = ENOTSUP; return -1, "File has no <write> method!");

    return file->ops->write(file->data, buf, nbyte);
}

static int devfs_lseek(struct file *fp, int off, int whence)
{
    struct devfs_file *file = (struct devfs_file *)fp->fl_data;

    WARN_ON(file->ops->lseek == NULL, errno = ENOTSUP; return -1, "File has no <lseek> method!");

    return file->ops->lseek(file->data, off, whence);
}

static int devfs_ioctl(struct file *fp, int request, va_list valist)
{
    struct devfs_file *file = (struct devfs_file *)fp->fl_data;

    WARN_ON(file->ops->ioctl == NULL, errno = ENOTSUP; return -1, "File has no <ioctl> method!");

    return file->ops->ioctl(file->data, request, valist);
}

static int devfs_sync(struct file *fp)
{
    struct devfs_file *file = (struct devfs_file *)fp->fl_data;

    WARN_ON(file->ops->sync == NULL, errno = ENOTSUP; return -1, "File has no <sync> method!");

    return file->ops->sync(file->data);
}

static int devfs_stat(struct mount_point *mp, const char *path, struct stat *stat)
{
    struct devfs_file *file = __get_devfile(path);

    WARN_ON(file == NULL, errno = EBADF; return -1, "File\"%s\" not exist!", path);

    memset(stat, 0, sizeof(struct stat));

    if (file->ops->read != NULL) {
        stat->st_mode |= 0444;
    }

    if (file->ops->write != NULL) {
        stat->st_mode |= 0222;
    }

    if (file->ops->lseek != NULL) {
        stat->st_mode |= S_IFBLK;
    } else {
        stat->st_mode |= S_IFCHR;
    }

    if (file->ops->stat != NULL) {
        return file->ops->stat(file->data, stat);
    }

    return 0;
}

static int devfs_opendir(struct mount_point *mp, struct dir *dp, const char *path)
{
    uint32_t *offset;

    WARN_ON(*path != '\0', errno = ENOENT; return -1, "Dir under devfs not supported for now!");

    offset = (uint32_t *)malloc(sizeof(uint32_t));

    WARN_ON(offset == NULL, return -1, "Fail to allocate memory for offset!");

    *offset = 0;
    dp->dr_data = (uintptr_t)offset;

    return 0;
}

static int devfs_readdir(struct mount_point *mp, struct dir *dp, struct dirent *de)
{
    dlist_t *iter;
    uint32_t i = 0;
    struct devfs_file *file;
    uint32_t *offset = (uint32_t *)dp->dr_data;

    dlist_foreach(iter, &devfs_files)
    {
        if (i++ == *offset) {
            file = container_of(iter, struct devfs_file, node);

            strncpy(de->de_name, file->name, CONFIG_MAX_FILE_NAME_LEN);

            de->de_type = "file";
            de->de_size = file->ops->size == NULL ? 0 : file->ops->size(file->data);

            *offset = *offset + 1;

            return 0;
        }
    }

    return -1;
}

static int devfs_closedir(struct mount_point *mp, struct dir *dp)
{
    uint32_t *offset = (uint32_t *)dp->dr_data;

    free(offset);

    return 0;
}

static int devfs_mount(struct mount_point *mp, va_list valist)
{
    static bool devfs_mounted = false;

    if (devfs_mounted) {
        return 0;
    }

    dlist_init(&devfs_files);

    return 0;
}

static const struct f_ops devfs_fops = {
    .open = devfs_open,
    .close = devfs_close,
    .read = devfs_read,
    .write = devfs_write,
    .lseek = devfs_lseek,
    .ioctl = devfs_ioctl,
    .sync = devfs_sync,
};

static const struct m_ops devfs_mops = {
    .stat = devfs_stat,
    .opendir = devfs_opendir,
    .readdir = devfs_readdir,
    .closedir = devfs_closedir,
    .mount = devfs_mount,
};

static struct file_system devfs = {
    "devfs",
    &devfs_fops,
    &devfs_mops,
};

/**
 * devfs_add_file - device filesystem add file
 * @name: the file name in the device filesystem mount point
 * @ops:  the device file operation methods
 * @data: the private data of the device file (arguments to operation routines)
 *
 * return : 0 on success, negative value on error
 */
int devfs_add_file(const char *name, const struct devfs_ops *fops, uintptr_t data)
{
    struct devfs_file *file;

    WARN_ON(name == NULL || strchr(name, '/') != NULL, return -1, "Invalid name!");

    /* CONFIG_MAX_FILE_NAME_LEN - 1 for the id and '\0' */

    WARN_ON(strlen(name) >= CONFIG_MAX_FILE_NAME_LEN - 1, return -1, "Invalid name!");

    WARN_ON(fops == NULL, return -1, "Invalid fops!");

    file = (struct devfs_file *)malloc(sizeof(struct devfs_file));

    if (unlikely(file == NULL)) {
        return -1;
    }

    file->ops = fops;
    file->data = data;
    file->name = name;

    dlist_add_tail(&devfs_files, &file->node);

    return 0;
}

/**
 * devfs_init - device filesystem initialization routine
 *
 * return : 0 on success, negative value on error
 */
static int devfs_init(void)
{
    /* vfs_fs_register here should never fail */

    (void)vfs_fs_register(&devfs);

    return vfs_mount("devfs", "/dev/");
}

MODULE_INIT(postkernel, devfs_init);
